/***************************************************************************
* @ProjectName	Hypnotizer ScreenSaver									   *
* @Description	Simple black & white spiral screenSaver					   *
* @Author		David Gonzalez Garcia.									   *
* @Web			davidxl.es												   *
* @license		No license, Open source									   *
* @date			02-12-2011												   *
* @version		1.1														   *
* @inspiration	http://incubator.quasimondo.com/processing/hypnosis.php	   *
***************************************************************************/

// #define DEBUG

/***  TODO  ***/
// fix preview bug

#include <windows.h>
#include <math.h>
#include "ids.h"

const double PI = 3.14159265358979323846;

char szAppName[] = "Hypnotizer ScreenSaver";
const int	timer = 1;
HWND		previewHwnd = NULL;
HBITMAP		Render_bmp = NULL;
HDC			Render_hdc, wdc;
LPBYTE		pixels;
LPBYTE*		frames = NULL;
int 		width, height, top, left, size, numFrames;

BITMAPFILEHEADER bmFile;
BYTE data[sizeof(BITMAPINFOHEADER) + 8] = {0};
BITMAPINFO* bmInfo = (BITMAPINFO*)data;

// Settings
int speed = 1;
int thick = 50;
int style = 1;
int maxFPS = 60;

const TCHAR regKey[] = "Software\\DavidXL\\ScreenSaver\\Hypnotizer";

inline void SaveSettings()
{
	HKEY hKey;
    if(!RegOpenKeyEx(HKEY_CURRENT_USER, regKey, 0, KEY_SET_VALUE, &hKey) ||
	   !RegCreateKeyEx(HKEY_CURRENT_USER, regKey, 0, 0, 0, KEY_QUERY_VALUE, 0, &hKey, 0))
    {
    	RegSetValueEx(hKey, "speed", 0, REG_DWORD, (LPBYTE)&speed, 4);
    	RegSetValueEx(hKey, "thick", 0, REG_DWORD, (LPBYTE)&thick, 4);
    	RegSetValueEx(hKey, "style", 0, REG_DWORD, (LPBYTE)&style, 4);
    	RegSetValueEx(hKey, "maxFPS",0, REG_DWORD, (LPBYTE)&maxFPS,4);
    }
    RegCloseKey(hKey);
}

inline void LoadSettings()
{
	HKEY hKey;
    if(!RegOpenKeyEx(HKEY_CURRENT_USER, regKey, 0, KEY_QUERY_VALUE, &hKey))
    {
    	DWORD nBytes = 4, type = REG_DWORD;
    	RegQueryValueEx(hKey, "speed", 0, &type, (LPBYTE)&speed, &nBytes);
    	nBytes = 4;
    	RegQueryValueEx(hKey, "thick", 0, &type, (LPBYTE)&thick, &nBytes);
    	nBytes = 4;
    	RegQueryValueEx(hKey, "style", 0, &type, (LPBYTE)&style, &nBytes);
    	nBytes = 4;
    	RegQueryValueEx(hKey, "maxFPS",0, &type, (LPBYTE)&maxFPS,&nBytes);
    }
    RegCloseKey(hKey);
}

// Render
inline void render()
{
	static unsigned int t = 0;
	memcpy(pixels, frames[t++], size);
	if (t >= numFrames)
		t = 0;
}

inline void preRender()
{
	double stVal = style ? thick / PI * (float) style : 0;
	int x, y, x2 = width >> 1, y2 = height >> 1;
	unsigned int t, padding = (numFrames * speed) << 1, pos = 0;

	double* cache = malloc(size * sizeof(double));
	frames = malloc(numFrames * sizeof(void*));

	for (y = -y2; y < y2; y++)
	{
		int yy = y*y;
		for (x = -x2; x < x2; x++)
			cache[pos++] = atan2(y, x) * stVal + sqrt(x*x + yy);
	}

	for (t = 0; t < numFrames; t++)
	{
		frames[t] = malloc(size);
		memset(frames[t], 0, size);
		for (pos = 0; pos < size; pos++)
			if ((int)((padding + t*speed + cache[pos]) / thick) & 1)
				frames[t][pos] = 1;

		memcpy(pixels, frames[t], size);
		TextOut(Render_hdc, 0, 0, "Loading...", 11);
		BitBlt(wdc, 0, 0, width, height, Render_hdc, 0, 0, SRCCOPY);
	}

	free(cache);
	return;
}

// Window procedures & winMain
LRESULT CALLBACK ScreenSaverProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {

    case WM_TIMER:
    {
        BitBlt(wdc, 0, 0, width, height, Render_hdc, 0, 0, SRCCOPY);
        render();
    }
    break;

    //case WM_NCHITTEST:
    case WM_MOUSEMOVE:
    {
    	static int moves = 10;
		if (--moves) break;
    }
    case WM_KEYDOWN:
    case WM_MOUSEWHEEL:
    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
		if(previewHwnd == NULL)
			DestroyWindow(hwnd);
        break;

    case WM_CREATE:
    {
    	size = width * height;
		numFrames = (thick / speed) << 1;

        wdc = GetDC(hwnd);
        Render_hdc = CreateCompatibleDC(0);

		BYTE data[sizeof(BITMAPINFOHEADER) + 8] = {0};
        BITMAPINFO* bmInfo = (BITMAPINFO*)data;
		*bmInfo = (BITMAPINFO){{sizeof(BITMAPINFOHEADER), width,
						height, 1, 8, BI_RGB, size, 0, 0, 2, 0}};

        RGBQUAD* pal = (RGBQUAD*)((LPSTR)bmInfo + (WORD)(bmInfo->bmiHeader.biSize));
        pal[0] = (RGBQUAD){0, 0, 0, 0};
        pal[1] = (RGBQUAD){255, 255, 255, 0};

        Render_bmp = CreateDIBSection(Render_hdc, bmInfo, DIB_RGB_COLORS, (LPVOID*)&pixels, 0, 0);
        SelectObject(Render_hdc, Render_bmp);
    }
    break;

    case WM_SETCURSOR:
			SetCursor(previewHwnd ? LoadCursor(NULL, IDC_ARROW) : NULL);
        return TRUE;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;

    case WM_PAINT:
    {
    	if (frames == NULL)
    	{
			/*HFONT font = CreateFont(100, 35, 0, 0,
							FW_NORMAL, FALSE, FALSE, FALSE,
							ANSI_CHARSET, OUT_DEFAULT_PRECIS,
							CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
							DEFAULT_PITCH | FF_ROMAN,
							"Arial");

			SelectObject(wdc, font);*/
			TextOut(wdc, 0, 0, "Loading...", 11);

			preRender();
			SetTimer(hwnd, timer, 1000 / maxFPS, NULL);
			//DeleteObject(font);
    	}

        RECT r;
        PAINTSTRUCT ps;
        if (GetUpdateRect(hwnd, &r, 0))
        {
			if (BeginPaint(hwnd, &ps))
			{
				BitBlt(ps.hdc, r.left, r.top, r.right - r.left,
						r.bottom - r.top, Render_hdc, r.left,
						r.top, SRCCOPY);
				EndPaint(hwnd, &ps);
			}
        }
        return 0;
    }
    break;

    case WM_DESTROY:
        ReleaseDC(hwnd, wdc);
        KillTimer(hwnd, timer);
        DeleteDC(Render_hdc);
        DeleteObject(Render_bmp);

        int i;
		for (i = 0; i < numFrames; i++)
			free(frames[i]);
		free(frames);

        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

BOOL CALLBACK settingsWProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
	case WM_COMMAND:
        switch (LOWORD(wParam))
        {
		case IDC_BTN_SAVE:
			speed = GetDlgItemInt(hwnd, IDC_EDIT_SPEED, 0, TRUE);
			thick = GetDlgItemInt(hwnd, IDC_EDIT_THINK, 0, FALSE);
			style = GetDlgItemInt(hwnd, IDC_EDIT_STYLE, 0, FALSE);
			maxFPS = GetDlgItemInt(hwnd, IDC_EDIT_MAXFPS, 0, FALSE);
			SaveSettings();
		case IDC_BTN_QUIT:
			EndDialog(hwnd, wParam);
			return TRUE;
        }
        return FALSE;

    case WM_INITDIALOG:
		SetDlgItemInt(hwnd, IDC_EDIT_SPEED, speed, TRUE);
		SetDlgItemInt(hwnd, IDC_EDIT_THINK, thick, FALSE);
		SetDlgItemInt(hwnd, IDC_EDIT_STYLE, style, FALSE);
		SetDlgItemInt(hwnd, IDC_EDIT_MAXFPS, maxFPS, FALSE);
		return TRUE;

    case WM_CLOSE:
        EndDialog(hwnd, wParam);
		return TRUE;
    }
    return FALSE;
}


int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR args, int nCmdShow)
{
	/*
		ScreenSaver           - Show the Settings dialog box.
		ScreenSaver /c        - Show the Settings dialog box, modal to the foreground window.
		ScreenSaver /p <HWND> - Preview Screen Saver as child of window <HWND>.
		ScreenSaver /s        - Run the Screen Saver.
	*/

	LoadSettings();

	if (args[0] == '/')
	{
		switch (args[1] | 32) // toLower
		{
		case 'c':
			return DialogBox(hInst, MAKEINTRESOURCE(DLG_SETTINGS), NULL, (DLGPROC)settingsWProc);

		case 'a':
		case 'p':
		{
			previewHwnd = (HWND) atoi(args+3);
			if (!IsWindow(previewHwnd))
				return 0;

			RECT ParentRect;
			GetClientRect(previewHwnd, &ParentRect);

			width = ParentRect.right - ParentRect.left;
			height = ParentRect.bottom - ParentRect.top;
			top = 0;
			left = 0;
		}
		break;

		case 's':
			if (FindWindow(szAppName,szAppName))
				return TRUE;
		#ifdef DEBUG
			width = GetSystemMetrics(SM_CXSCREEN);
			height = GetSystemMetrics(SM_CYSCREEN);
			top = 0;
			left = 0;
		#else
			width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
			height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
			top = GetSystemMetrics(SM_YVIRTUALSCREEN);
			left = GetSystemMetrics(SM_XVIRTUALSCREEN);
		#endif
			break;

		default:
			MessageBox(NULL, "cmd args fail", "Error!", MB_ICONEXCLAMATION | MB_OK);
		}
	}
	else
	{
		return DialogBox(hInst, MAKEINTRESOURCE(DLG_SETTINGS), NULL, (DLGPROC)settingsWProc);
	}


    WNDCLASSEX wc = {sizeof(WNDCLASSEX), 0, ScreenSaverProc, 0, 0,
					hInst, LoadIcon(NULL, IDI_APPLICATION),
					LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOWTEXT),
					NULL, szAppName, LoadIcon(NULL, IDI_APPLICATION)};

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", szAppName, MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    HWND hwnd = CreateWindowEx(WS_EX_TOPMOST, szAppName, szAppName,
				WS_POPUP, left, top, width, height, NULL, NULL, hInst, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", szAppName, MB_ICONERROR | MB_OK);
        return 0;
    }

    if (previewHwnd)
	{
		SetParent(hwnd, previewHwnd);

		// Make this a child window so it will close when the parent dialog closes
		// GWL_STYLE = -16, WS_CHILD = 0x40000000
		SetWindowLong(hwnd, -16, GetWindowLong(hwnd, -16) | 0x40000000);
	}

    ShowWindow(hwnd, nCmdShow);
    //UpdateWindow(hwnd);

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}


